package sidecar

import (
	"archive/zip"
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"os"
	"path/filepath"

	"gitee.com/zh-jn/harmonica-previewer-online/api"
	previewer "gitee.com/zh-jn/harmonica-previewer-online/previewer/harmonica/v1"
	"github.com/gorilla/websocket"
)

type Config struct {
	Addr string
	Port int

	PreviewerPort int
	JsBundlePath  string
}

type SideCar struct {
	cfg           Config
	connPreviewer *websocket.Conn
}

func NewSideCar(cfg Config) (*SideCar, error) {
	ret := &SideCar{
		cfg: cfg,
	}

	backendUrl := fmt.Sprintf("ws://127.0.0.1:%v", cfg.PreviewerPort)
	dialer := websocket.DefaultDialer
	requestHeader := http.Header{}
	connBackend, _, err := dialer.Dial(backendUrl, requestHeader)
	if err != nil {
		return nil, err
	}
	ret.connPreviewer = connBackend
	return ret, nil
}

func (s *SideCar) localJsbundlePath(jsBundleId, sessionId string) string {
	return filepath.Join(s.cfg.JsBundlePath, fmt.Sprintf("%v_%v", jsBundleId, sessionId))
}

func (s *SideCar) SetJsBundle(jsBundleId, sessionId string, reader io.Reader) error {
	jsBundlePath := s.localJsbundlePath(jsBundleId, sessionId)
	// clean jsBundlePath
	if err := os.RemoveAll(jsBundlePath); err != nil {
		return err
	}

	if err := os.MkdirAll(jsBundlePath, 0666); err != nil {
		return err
	}

	buf, err := ioutil.ReadAll(reader)
	if err != nil {
		return err
	}

	// debug
	if err = ioutil.WriteFile(filepath.Join(jsBundlePath, "debug.zip"), buf, os.ModePerm); err != nil {
		return err
	}

	size := int64(len(buf))
	bufReader := bytes.NewReader(buf)
	zipReader, err := zip.NewReader(bufReader, size)
	if err != nil {
		return fmt.Errorf("new zip reader err, %v", err)
	}
	for _, f := range zipReader.File {
		if f.FileInfo().IsDir() {
			hostPath := filepath.Join(jsBundlePath, f.Name)
			err = os.MkdirAll(hostPath, 0666)
			if err != nil {
				return fmt.Errorf("mkdir err, %v", err)
			}
		}
	}

	for _, f := range zipReader.File {
		if !f.FileInfo().IsDir() {
			hostPath := filepath.Join(jsBundlePath, f.Name)
			hostdir := filepath.Dir(hostPath)
			if err := os.MkdirAll(hostdir, 0666); err != nil {
				return fmt.Errorf("mkdir [%v] err, %v", hostdir, err)
			}
			fr, err := f.Open()
			if err != nil {
				return fmt.Errorf("open file [%v] in zip err, %v", f.Name, err)
			}
			defer fr.Close()
			buf, err := ioutil.ReadAll(fr)
			if err != nil {
				return fmt.Errorf("read file [%v] in zip err, %v", f.Name, err)
			}
			if err = ioutil.WriteFile(hostPath, buf, os.ModePerm); err != nil {
				return fmt.Errorf("write zip file [%v] to local [%v] err, %v", f.Name, hostPath, err)
			}
		}
	}

	cmsg := previewer.PreviewerCMessage{
		Name:    "plugin",
		Command: "restart",
		Args: map[string]string{
			"jsBundle": jsBundlePath,
		},
	}

	cmsgBuf, err := json.Marshal(cmsg)
	if err != nil {
		return err
	}

	err = s.connPreviewer.WriteMessage(websocket.BinaryMessage, cmsgBuf)
	if err != nil {
		return fmt.Errorf("send cmsg to backent err, %v", err)
	}
	return nil
}

func RunServer(cfg Config) {
	sidecar, err := NewSideCar(cfg)
	if err != nil {
		panic(err)
	}
	if err = http.ListenAndServe(
		fmt.Sprintf("%v:%v", cfg.Addr, cfg.Port),
		http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			jsbundleId := r.URL.Query().Get("jsbundleid")
			if jsbundleId == "" {
				w.WriteHeader(http.StatusOK)
				w.Write(api.RspBuf(nil, fmt.Errorf("missing 'jsbundleid' in query")))
				return
			}
			sessionId := r.URL.Query().Get("sessionid")
			if sessionId == "" {
				w.WriteHeader(http.StatusOK)
				w.Write(api.RspBuf(nil, fmt.Errorf("missing 'sessionid' in query")))
				return
			}
			err := sidecar.SetJsBundle(jsbundleId, sessionId, r.Body)
			defer r.Body.Close()
			if err != nil {
				w.WriteHeader(http.StatusOK)
				w.Write(api.RspBuf(nil, err))
				return
			}
			w.WriteHeader(http.StatusOK)
			w.Write(api.RspBuf(nil, nil))
			return
		},
		)); err != nil {
		panic(err)
	}
}
